﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
using System.ServiceModel;
using System.ServiceModel.Description;

namespace GoodStuff.Heartbeat.Core
{
    /// <summary>
    /// The processor will read all sensor data, log them and apply monitors on them. When a monitor fails, all applicable notifiers are fired.
    /// Notifiers have their own responsibility not to 'spam' the users.
    /// </summary>
    public class Engine
    {
        IList<IComponent> _components;
        //IList<IMonitor> _monitors;

        Timer timer;
        Timer cleanupTimer;
        ServiceHost serviceHost;

        public event EventHandler<ExceptionEventArgs> OnError;
        public event EventHandler<TraceEventArgs> OnTrace;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="components">Created by the configuration loader.</param>
        public Engine(IList<IComponent> components)
        {
            _components = components;

            timer = new Timer();
            timer.Interval = TimeSpan.FromSeconds(30).TotalMilliseconds;
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);

            cleanupTimer = new Timer();
            cleanupTimer.Interval = TimeSpan.FromHours(12).TotalMilliseconds;
            cleanupTimer.Elapsed += new ElapsedEventHandler(cleanupTimer_Elapsed);
        }

        void cleanupTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            AutoClean();
        }

        void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            try
            {
                //TODO Investigate timer classes and event reentrance, e.g. http://msdn.microsoft.com/nl-nl/magazine/cc164015(en-us).aspx   
                using (var db = new GoodStuff.Data.DataContextBuilder().EnableObjectTracking().Build())
                {
                    DateTime timeStamp = DateTime.Now;

                    int sensorId = 1;

                    foreach (IComponent component in _components)
                    {
                        foreach (var sensor in component.Sensors)
                        {
                            try
                            {
                                sensor.Update();

                                //monitor.Validate(channel);
                                //notifier.Notify(monitor);

                                int channelNo = 0;

                                foreach (var channel in sensor.Values)
                                {
                                    //Console.WriteLine(string.Format("{0}: {1} {2}", channel.Description, channel.Value, channel.Units));
                                    db.InsertOnSubmit<Core.Data.SensorReading>(new Core.Data.SensorReading() { SensorID = sensorId, Channel = channelNo, Timestamp = timeStamp, Value = channel.Value });
                                    channelNo++;
                                }
                            }
                            catch (Exception ee)
                            {
                                this.OnError(this, new ExceptionEventArgs(new Exception(component.Name + " " + sensor.Name + ":" + ee.Message, ee)));
                                //Console.WriteLine(component.Name + " " + sensor.Name + ":" + ee.Message);
                                //Log the exception when counting, but mark the event as missing data.
                            }

                            sensorId++;
                        }
                    }

                    db.SubmitChanges();
                }
            }
            catch (Exception ee)
            {
                this.OnError(this, new ExceptionEventArgs(new Exception("Sensor retrieval failed.", ee)));
                //TODO write to the eventlog.
            }
        }

        public void AutoClean()
        {
            try
            {
                this.OnTrace(this, new TraceEventArgs("Starting data aggregation"));
                Data.Aggregator aggregator = new Data.Aggregator();

                int sensorId = 1;
                foreach (IComponent component in _components)
                {
                    foreach (var sensor in component.Sensors)
                    {
                        int channelNo = 0;

                        foreach (var channel in sensor.Values)
                        {
                            aggregator.Aggregate(sensorId, channelNo, DateTime.Today, 5);
                            channelNo++;
                        }

                        sensorId++;
                    }
                }
                this.OnTrace(this, new TraceEventArgs("Data aggregation completed."));
            }
            catch (Exception ee)
            {
                this.OnError(this, new ExceptionEventArgs(new Exception("Data aggregation failed.", ee)));
            }
        }
                  

        public void Start()
        {
            serviceHost = new ServiceHost(typeof(Core.SensorService), new Uri("http://localhost:6666/SensorService"));
            serviceHost.AddServiceEndpoint(typeof(ISensorService), new BasicHttpBinding(), "");
            serviceHost.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });

            // Open the ServiceHost to start listening for messages.
            serviceHost.Open();

            timer.Start();
            //timer will only fire after the interval elapsed, so kick it off right now.
            timer_Elapsed(this, null);

            cleanupTimer.Start();
            cleanupTimer_Elapsed(this, null);
        }

        public void Stop()
        {
            if (timer != null)
            {
                timer.Stop();
                timer = null;
            }

            if (cleanupTimer != null)
            {
                cleanupTimer.Stop();
                cleanupTimer = null;
            }

            // Close the ServiceHost.
            if (serviceHost != null)
            {
                serviceHost.Close();
            }
        }
    }

    public class ExceptionEventArgs : EventArgs
    {
        public ExceptionEventArgs(Exception error)
        {
            Error = error;
        }

        public Exception Error { get; private set; }
    }

    public class TraceEventArgs : EventArgs
    {
        public TraceEventArgs(string message)
        {
            Message = message;
        }

        public string Message { get; private set; }
    }
}
